#!/usr/bin/env ruby

LKP_SRC = ENV['LKP_SRC'] || File.dirname(File.dirname(File.realpath($PROGRAM_NAME)))

require 'optparse'
require 'ostruct'
require "#{LKP_SRC}/lib/yaml"
require "#{LKP_SRC}/lib/stats"
require "#{LKP_SRC}/lib/result"
require "#{LKP_SRC}/lib/log"

$opt_group = 'suite' # common choices: test, _rt
$opt_pattern = ''

opt_parser = OptionParser.new do |opts|
  opts.banner = 'Usage: lkp kpi [options]'

  opts.separator ''
  opts.separator 'options:'

  opts.on('-g FIELD', '--group-by FIELD', 'group by FIELD') do |field|
    $opt_group = field.gsub '/', " + '/' + "
  end

  opts.on('-p PATTERN', '--pattern PATTERN', 'grep RESULT_ROOT with PATTERN') do |pattern|
    $opt_pattern << " #{pattern}"
  end

  opts.on('-d DATE', '--date DATE', "search latest DATE days's RESULT_ROOT") do |date|
    case date
    when /[0-9]+m$/, /[0-9]+w$/, /[0-9]+d$/, /[0-9]+$/
      $opt_pattern = "  -d #{date}" + $opt_pattern
    else
      log_warn "-d #{date}: parameter is unsuitable."
      puts opts
      exit
    end
  end

  opts.on('-r', '--raw-samples', 'show raw samples') do
    $opt_raw = true
  end

  opts.on_tail('-h', '--help', 'show this message') do
    puts opts
    exit
  end
end

opt_parser.parse!(ARGV)

$kpi_weights = {} # kpi_name => kpi_weight
$kpi_baselines = {} # kpi_name => kpi_baseline

def load_meta_file(program, file)
  return unless File.exist? file
  $kpi_weights[program] ||= {}
  $kpi_baselines[program] ||= {}
  meta = YAML.load_file(file)
  if Hash === meta['results']
    meta['results'].each do |stat, attr|
      if attr and attr['kpi']
        $kpi_weights[program][stat] = attr['kpi']
        $kpi_baselines[program][stat] = attr['baseline']
      end
    end
  end
end

def load_meta(program)
  return if $kpi_weights.include? program
  load_meta_file(program, "#{LKP_SRC}/programs/#{program}/meta.yaml") or
  load_meta_file(program, "#{LKP_SRC}/setup/#{program}/meta.yaml")
end

_result_roots = `#{LKP_SRC}/lkp-exec/_rt #{$opt_pattern}`.split
groups = {}
_result_roots.each do |_rt|
  _rt = _rt.sub(/^~/, ENV['HOME'])
  __rt = File.dirname _rt

  matrix_path = "#{_rt}/matrix.json"
  next unless File.size? matrix_path

  matrix = load_json matrix_path
  next unless matrix

  next if (matrix['last_state.is_incomplete_run'] &&
           matrix['last_state.is_incomplete_run'].sum == matrix_cols(matrix))

  result_path = ResultPath.new
  result_path.parse_result_root(_rt)
  result_path['_rt'] = _rt
  result_path['__rt'] = __rt

  jobfile = Dir.glob("#{_rt}/?/job.yaml").last
  job = YAML.load_file(jobfile)
  job['_rt'] = _rt
  job['test'] = result_path.test_desc(/commit/, true) # e.g. "stream/100000000-100%"
  
  group = job[$opt_group]
  groups[group] ||= {}

  if Hash === job['pp']
    job['pp'].each do |program, params|
      load_meta(program)
      unless $kpi_weights.include? program
        puts "Skip #{program} due to no meta.yaml"
        next
      end
      $kpi_weights[program].each do |kpi_name, kpi_weight|
        next unless matrix[program + '.' + kpi_name]
        groups[group][kpi_name] ||= []
        if $opt_group == 'suite'
          groups[group][kpi_name].append matrix[program + '.' + kpi_name].average
        else
          groups[group][kpi_name].concat matrix[program + '.' + kpi_name]
        end
      end
    end
  end
end

unless $opt_raw
  printf "%8s %16s %6s%%  %20s %s\n",
    $opt_group == 'suite' ? 'TESTS' : 'RUNS',
    'AVERAGE', 'STDDEV', 'KPI', $opt_group
end

scenes = {}  # suite => geometry_mean (relative to baseline and scaled x100)

groups.each do |k, v|
  kpi_logsum = 0
  kpi_nr = 0
  v.each do |kpi_name, kpi_vals|
    if $opt_raw
      puts "#{k}.#{kpi_name}: #{kpi_vals.join ' '}"
    else
      next if kpi_vals.empty?

      if $opt_group == 'suite'
        avg = kpi_vals.geometry_mean
        kpi_weight = $kpi_weights[k][kpi_name]
        kpi_nr += kpi_weight
        if $kpi_baselines[k][kpi_name]
          kpi_baseline = $kpi_baselines[k][kpi_name]
        else
          puts "Warning: missing KPI baseline #{k}.#{kpi_name}, SCORE will be inaccurate"
          kpi_baseline = avg
        end
        kpi_logsum += kpi_weight * Math.log(avg / kpi_baseline)
      else
        avg = kpi_vals.average
      end
      stddev = kpi_vals.standard_deviation
      stddev_percent = 100 * stddev / avg
      printf "%8d %16.2f %6d%%  %20s %s\n", kpi_vals.size, avg, stddev_percent, kpi_name, k
    end
  end
  if kpi_nr > 0
    scenes[k] = 100 * Math.exp(kpi_logsum / kpi_nr)
  end
end

exit if scenes.empty?

printf "\n%16s %6s\n", 'SCENE', 'SCORE'
scenes.each do |scene, score|
  printf "%16s %6d\n", scene, score
end
printf "%16s %6d\n", 'ALL', scenes.values.average
